#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <curl/curl.h>
#include <curl/types.h>
#include <curl/easy.h>
#include "commlib.h"

#define COOKIE_MAX_LEN 1024
#define GB2312 "gb2312"

struct memory
{
	char *data;
	size_t size;
};

char *get_cookie(CURL *curl);

struct memory html_data;

size_t display_header(void *ptr, size_t size, size_t nmemb, void *stream)
{
	fwrite(ptr, size, nmemb, stdout);
	return size * nmemb;
}

size_t write_memory_callback(void *ptr, size_t size, size_t nmemb, void *stream)
{
	size_t realsize = size * nmemb;
	struct memory *mem = (struct memory *)stream;
	
	mem->data = realloc(mem->data, mem->size + realsize + 1);
	if (mem->data) {
		memcpy(&(mem->data[mem->size]), ptr, realsize);
		mem->size += realsize;
		mem->data[mem->size] = '\0';
	}

	return realsize;
}

static void free_memory(struct memory *p)
{
	p ? (free(p->data), p->data = NULL, p->size = 0) : 1;
}
/*
size_t save_data(void *ptr, size_t size, size_t nmemb, void *stream)
{
	return fwrite(ptr, size, nmemb, stream);
}
*/

void setopt_comm(CURL *curl)
{
	struct curl_slist *slist = NULL;

	slist = curl_slist_append(slist, "Accept: image/gif, image/x-xbitmap, image/jpeg, "
			"image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, "
			"application/vnd.ms-powerpoint, application/msword, */*");
	slist = curl_slist_append(slist, "Accept-Language: zh-cn");
	slist = curl_slist_append(slist, "User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; CNCDialer)");
	slist = curl_slist_append(slist, "Connection: Keep-Alive");
	curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
	//curl_slist_free_all(slist);
	//curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, display_header);
	curl_easy_setopt(curl, CURLOPT_ENCODING, "");
	curl_easy_setopt(curl, CURLOPT_HTTP_CONTENT_DECODING, 1);
	curl_easy_setopt(curl, CURLOPT_HTTP_TRANSFER_DECODING, 1);
	curl_easy_setopt(curl, CURLOPT_COOKIEFILE, "");
	curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
	curl_easy_setopt(curl, CURLOPT_WRITEDATA, &html_data);
	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_memory_callback);
}

void set_login(CURL *curl, const char *post_url, const char *referer_url, const char *post_content)
{
	curl_easy_setopt(curl, CURLOPT_URL, post_url);
	curl_easy_setopt(curl, CURLOPT_REFERER, referer_url);
	curl_easy_setopt(curl, CURLOPT_POST301, 1);
	curl_easy_setopt(curl, CURLOPT_POST, 1);
	curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS, post_content);
}

int login(CURL *curl, const char *id, const char *passwd, const char *cookie_file)
{
	char post_content[512];
	int re_login_flag = 0;
	sprintf(post_content, "id=%s&passwd=%s", id, passwd);
	set_login(curl, "www.newsmth.net/bbslogin.php", 
			"www.newsmth.net/bbsguseleft.html", 
			post_content);

	CURLcode res;
re_login:
	res = curl_easy_perform(curl);
	if (!res) {
		long res_code;
		char *cookie = NULL;
		curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &res_code);
		switch (res_code) {
			case 200:
				cookie = get_cookie(curl);
				if (!cookie) {
					if (re_login_flag) {
						fprintf(stderr, "login failed\n");
						return -1;
					}
					printf("need kick\n");
					re_login_flag = 1;
					strcat(post_content, "&kick_multi=1");
					set_login(curl, "www.newsmth.net/bbslogin.php?mainurl=",
							"www.newsmth.net/bbslogin.php",
							post_content);
					goto re_login;
				}
				FILE *fp = fopen(cookie_file, "w");
				if (!fp) {
					char buf[32];
					sprintf(buf, "write cookie file %s failed", cookie_file);
					perror(buf);
					return -1;
				}
				fputs(cookie, fp);
				fclose(fp);
				free(cookie);
				break;
			default:
				fprintf(stderr, "login failed\n");
				return -1;
		}
	}
	free_memory(&html_data);
	return 0;
}

void set_logout(CURL *curl, const char *url, const char *referer_url, const char *cookie)
{
	if (!url) {
		printf("logout failed!\n");
		return;
	}
	curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);
	curl_easy_setopt(curl, CURLOPT_URL, url);
	referer_url ? curl_easy_setopt(curl, CURLOPT_REFERER, referer_url) : 1;
	cookie ? curl_easy_setopt(curl, CURLOPT_COOKIE, cookie) : 1;
	curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 0);
}

int logout(CURL *curl)
{
	set_logout(curl, "http://www.newsmth.net/bbslogout.php", 
			"http://www.newsmth.net/bbsleft.php", NULL/*cookie*/);
	CURLcode res;
	res = curl_easy_perform(curl);
	free_memory(&html_data);
	if (res == 0)
		return 0;
	return -1;
}

char *get_cookie(CURL *curl)
{
	char *ret = malloc(COOKIE_MAX_LEN);
	struct curl_slist *cookie_list, *t = NULL;
	char *p = NULL, *p1 = NULL, *ptr = ret;

	memset(ret, 0, COOKIE_MAX_LEN);
	curl_easy_getinfo(curl, CURLINFO_COOKIELIST, &cookie_list);
	t = cookie_list;
	if (!t) {
		free(ret);
		ret = NULL;
	}
	else {
		do {
			//printf("%s\n", t->data);
			if (ptr != ret) {
				strcat(ptr, "; ");
				ptr += strlen(ptr);
			}
			p = strrchr(t->data, '\t');
			*p = '\0';
			p++;
			p1 = strrchr(t->data, '\t');
			p1++;
			strcpy(ptr, p1);
			strcat(ptr, "=");
			strcat(ptr, p);
			//strcat(ptr, "; ");
			ptr += strlen(ptr);
			t = t->next;
		} while (t);
	}
	curl_slist_free_all(cookie_list);
	return ret;
}

int get_html_charset(const char *html, char *charset, int charset_size)
{
	char *p, *p1;
	char *html_temp = strdup(html);
	p = strstr(html_temp, "<meta");
	if (p) {
		p1 = strstr(p, "/>");
		if (p1) {
			*p1 = '\0';
			p1 = strstr(p, "content");
			if (p1) {
				p = strstr(p1, "charset");
				if (p) {
					p1 = strchr(p, '=');
					if (p1) {
						p1++;
						p = strchr(p1, '"');
						if (p) {
							*p = '\0';
						}
						if (p - p1 >= charset_size) {
							free(html_temp);
							return -1;
						}
						strcpy(charset, p1);
						free(html_temp);
						return 0;
					}
				}
			}
		}
	}
	free(html_temp);
	return -1;
}

int new_tie(CURL *curl, const char *board, const char *title, const struct memory *text)
{
	if (!board) {
		printf("no board\n");
		return -1;
	}
	curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);

	char url[512];
	sprintf(url, "http://www.newsmth.net/bbsdoc.php?board=%s", board);
	curl_easy_setopt(curl, CURLOPT_REFERER, url);

	sprintf(url, "http://www.newsmth.net/bbspst.php?board=%s", board);
	curl_easy_setopt(curl, CURLOPT_URL, url);
	curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 0);

	CURLcode res;
	res = curl_easy_perform(curl);
	
	char charset[16];
	if (!get_html_charset(html_data.data, charset, sizeof charset)) {
		free_memory(&html_data);
		char post_url[512];
		sprintf(post_url, "www.newsmth.net/bbssnd.php?board=%s&reid=0", board);
		if (!strcasecmp(charset, GB2312)) {
			curl_easy_setopt(curl, CURLOPT_URL, post_url);
			curl_easy_setopt(curl, CURLOPT_REFERER, url);
			curl_easy_setopt(curl, CURLOPT_POST, 1);

			size_t post_content_size = text->size + (text->size + 1) / 2 
					+ strlen("title=") + strlen("&signature=0&text=") + 500;
			char *post_content = malloc(post_content_size);
			memset(post_content, 0, post_content_size);
			strcpy(post_content, "title=");
			const char *p; 
			char *pst_index = post_content + strlen("title=");
			for (p = title; *p; p++) {
				if (isascii(*p)) {
					if (*p != ' ')
						*pst_index = *p;
					else
						*pst_index = '+';
					pst_index++;
				}
				else {
					sprintf(pst_index, "%%%0hhX", *p);
					pst_index += strlen(pst_index);
				}
			}
			strcat(post_content, "&signature=0&text=");
			pst_index = post_content + strlen(post_content);

			for (p = text->data; p != text->data + text->size; p++) {
				if (isascii(*p)) {
					if (*p != ' ')
						*pst_index = *p;
					else
						*pst_index = '+';
					pst_index++;
				}
				else {
					sprintf(pst_index, "%%%0hhX", *p);
					pst_index += strlen(pst_index);
				}
			}
			curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS, post_content);
			CURLcode res;
			res = curl_easy_perform(curl);
			if (!res) {
				long res_code;
				curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &res_code);
				if (res_code == 200) {
					printf("send tie OK!\n");
				}
				else printf("send tie failed\n");
			}
			else printf("send tie failed\n");
		}
	}
	else free_memory(&html_data);

	return 0;
}

int main(int argc, char **argv)
{
	CURL *curl;
	int ch;
	char id[128], passwd[128];
	char tie_file[512], verify_file[512];
	char cookie_file[512] = "cookie";
	int logout_flag = 0;

	memset(id, 0, sizeof id);
	memset(passwd, 0, sizeof passwd);
	memset(tie_file, 0, sizeof tie_file);
	memset(verify_file, 0, sizeof verify_file);
	/*
	 * -u为用户名
	 * -p为密码
	 * -f指定要发的帖子的内容所在的文件,该文件的第一行为将要发帖的版面,第二行为帖子的主题,从第三行开始是帖子的内容
	 * -v指定验证码文件的保存文件名,如果不指定,则默认存放在当前目录的verify_picture文件中(可能带后缀)
	 * -q表示推出登录,会删除之前产生的cookie文件,需要使用-c指定cookie文件 
	 * -c指定之前登录时产生的cookie文件的位置,默认为当前目录的cookie文件,登录时指定该选项后,
	 *  	将把登录后获得的cookie存放在该文件中,
	 *  	若在登录时不指定该选项,则cookie存放在内存中,本程序退出时即丢弃cookie
	 * 如果不存在-u和-p选项，则需要有-c选项来指定之前获取的cookie文件,此举为了放置频繁登录推出
	 **/
	while ((ch = getopt(argc, argv, "u:p:f:v:qc:")) != -1) {
		switch (ch) {
			case 'u':
				if (!optarg) {
					printf("bad option\n");
					return 0;
				}
				strcpy(id, optarg);
				break;
			case 'p':
				if (!optarg) {
					printf("bad option\n");
					return 0;
				}
				strcpy(passwd, optarg);
				break;
			case 'f':
				if (!optarg) {
					printf("bad option\n");
					return 0;
				}
				strcpy(tie_file, optarg);
				break;
			case 'v':
				if (!optarg) {
					printf("bad option\n");
					return 0;
				}
				strcpy(verify_file, optarg);
				break;
			case 'q':
				logout_flag = 1;
				break;
			case 'c':
				if (!optarg) {
					printf("bad option\n");
					return 0;
				}
				strcpy(cookie_file, optarg);
				break;
			case '?':
				printf("unknown option: %c\n", (char)optopt);
				return 0;
		}
	}
	
	curl = curl_easy_init();
	if (curl) {
		setopt_comm(curl);
		if (strlen(id) !=0 && strlen(passwd) != 0) {
			printf("login...\n");
			if (login(curl, id, passwd, cookie_file) != 0)
				return 0;
			printf("login success\n");
		}
		else {
			char cookie[512];
			FILE *fp = fopen(cookie_file, "r");
			if (!fp) {
				fprintf(stderr, "get cookie failed\n");
				return 0;
			}
			fgets(cookie, sizeof cookie, fp);
			fclose(fp);
			char *p = strchr(cookie, '\n');
			p ? *p = '\0' : 1;
			curl_easy_setopt(curl, CURLOPT_COOKIE, cookie);
		}
		if (strlen(tie_file) > 0) {
			char board[4096], title[4096];
			FILE *fp = fopen(tie_file, "r");
			if (!fp) {
				fprintf(stderr, "send tie failed\n");
				return 0;
			}

			if (!fgets(board, sizeof board, fp)) {
				fprintf(stderr, "send tie failed\n"); 
				return 0;
			}
			char *p = strchr(board, '\n');
			p ? *p = '\0' : 1;
			p = strchr(board, '\r');
			p ? *p = '\0' : 1;

			if (!fgets(title, sizeof title, fp)) {
				fprintf(stderr, "send tie failed\n"); 
				return 0;
			}
			p = strchr(title, '\n');
			p ? *p = '\0' : 1;
			p = strchr(title, '\r');
			p ? *p = '\0' : 1;

			struct memory text = {NULL, 0};
			int n;
			text.data = malloc(1);
			text.size = 1;
			text.data[0] = '\0';
			while (1) {
				char tmp[4096];
				n = fread(tmp, 1, 4096, fp);
				text.data = realloc(text.data, text.size + n);
				memcpy(&text.data[text.size - 1], tmp, n);
				text.size += n;
				if (n < 4096 && feof(fp)) {
					break;
				}
				else if (n < 4096 && ferror(fp)) {
					fprintf(stderr, "read tie content failed\n");
					return 0;
				}
			}
			text.data[text.size - 1] = '\0';
			text.size--;
			if (new_tie(curl, board, title, &text) < 0) {
				fprintf(stderr, "send new tie failed\n");
			}
			fclose(fp);
			free_memory(&text);
		}
		if (logout_flag)
			logout(curl);
		curl_easy_cleanup(curl);
	}
	return 0;
}
